package com.sl.ms.carriage.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.*;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sl.ms.base.api.common.AreaFeign;
import com.sl.ms.carriage.domain.constant.CarriageConstant;
import com.sl.ms.carriage.domain.constant.RedisConstant;
import com.sl.ms.carriage.domain.dto.CarriageDTO;
import com.sl.ms.carriage.domain.dto.WaybillDTO;
import com.sl.ms.carriage.domain.enums.EconomicRegionEnum;
import com.sl.ms.carriage.entity.CarriageEntity;
import com.sl.ms.carriage.enums.CarriageExceptionEnum;
import com.sl.ms.carriage.handler.CarriageChainHandler;
import com.sl.ms.carriage.mapper.CarriageMapper;
import com.sl.ms.carriage.service.CarriageService;
import com.sl.ms.carriage.utils.CarriageUtils;
import com.sl.transport.common.exception.SLException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class CarriageServiceImpl extends ServiceImpl<CarriageMapper, CarriageEntity> implements CarriageService {

    @Resource
    private AreaFeign areaFeign;

    // 责任链模式
    @Resource
    private CarriageChainHandler carriageChainHandler;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 新增/修改运费模板
     *
     * @param carriageDto 新增/修改运费对象
     *                    必填字段：templateType、transportType
     *                    更新时传入id字段
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public CarriageDTO saveOrUpdate(CarriageDTO carriageDto) {
        // 要排除当前id的记录

        LambdaQueryWrapper<CarriageEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(CarriageEntity::getTemplateType, carriageDto.getTemplateType());
        // 运输类型：1-普快 2-特快
        lambdaQueryWrapper.eq(CarriageEntity::getTransportType, carriageDto.getTransportType());
        // 排除当前id的记录 因为要防止修改
        lambdaQueryWrapper.ne(ObjectUtil.isNotEmpty(carriageDto.getId()), CarriageEntity::getId, carriageDto.getId());

        List<CarriageEntity> list = list(lambdaQueryWrapper);

        // 没有记录就是新增/或者修改
        if (CollUtil.isEmpty(list)) {
            return saveOrUpdateCarriage(carriageDto);
        }

        // 如果有重复就看是否是经济区互寄
        if (ObjectUtil.notEqual(carriageDto.getTemplateType(), CarriageConstant.ECONOMIC_ZONE)) {
            throw new SLException(CarriageExceptionEnum.NOT_ECONOMIC_ZONE_REPEAT);
        }

        //
        List<String> city = new ArrayList<>();
        for (CarriageEntity carriageEntity : list) {
            String associatedCity = carriageEntity.getAssociatedCity();
            // 不能为NULL
            if (StrUtil.isEmpty(associatedCity)) {
                continue;
            }
            // 进行分割
            String[] strings = StrUtil.splitToArray(associatedCity, ",");
            Collections.addAll(city, strings);
        }
        // 下面就是判断是否有重复的城市
        Collection<String> intersection = CollUtil.intersection(city, carriageDto.getAssociatedCityList());
        if (CollUtil.isNotEmpty(intersection)) {
            throw new SLException(CarriageExceptionEnum.ECONOMIC_ZONE_CITY_REPEAT);
        }
        // 没有重复就是新增/或者修改
        return saveOrUpdateCarriage(carriageDto);

    }

    /**
     * 保存或者更新运费模板
     *
     * @param carriageDto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public CarriageDTO saveOrUpdateCarriage(CarriageDTO carriageDto) {
        CarriageEntity carriageEntity = CarriageUtils.toEntity(carriageDto);
        boolean saveOrUpdate = saveOrUpdate(carriageEntity);
        if (saveOrUpdate) {
            // 删除redis 直接把key下面的所有的数据都删除
            stringRedisTemplate.delete(Objects.requireNonNull(stringRedisTemplate.keys(RedisConstant.CARRIAGE_TEMPLATE + "*")));
            // 返回是因为多一个id
            return CarriageUtils.toDTO(carriageEntity);
        }
        return null;
    }

    /**
     * 获取全部运费模板
     *
     * @return 运费模板对象列表
     */
    @Override
    public List<CarriageDTO> findAll() {
        LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.<CarriageEntity>lambdaQuery().orderByDesc(CarriageEntity::getCreated);
        List<CarriageEntity> list = this.list(queryWrapper);
        if (list != null && !list.isEmpty()) {
            return list.stream().map(CarriageUtils::toDTO).collect(java.util.stream.Collectors.toList());
        }

        // 否则就是返回null
        return new ArrayList<CarriageDTO>();
    }


    /**
     * 运费计算
     *
     * @param waybillDTO 运费计算对象
     * @return 运费模板对象，不仅包含模板数据还包含：computeWeight、expense 字段
     */
    @Override
    public CarriageDTO compute(WaybillDTO waybillDTO) {

        // TODO 存入redis将CarriageEntity存入
        Long receiverCityId = waybillDTO.getReceiverCityId();
        Long senderCityId = waybillDTO.getSenderCityId();
        String key = RedisConstant.CARRIAGE_TEMPLATE + receiverCityId + ":" + senderCityId;

        String jsonStr = stringRedisTemplate.opsForValue().get(key);

        CarriageEntity carriage = null;
        if (StrUtil.isNotEmpty(jsonStr)) {
            carriage = JSONUtil.toBean(jsonStr, CarriageEntity.class);
        } else {
            // 1、根据前端传递过来的城市id查询运费模板
            carriage = this.findCarriage(waybillDTO);
            carriage = this.findCarriageWithChain(waybillDTO);
            // 转为json字符串
            jsonStr = JSONUtil.toJsonStr(carriage);
            // 存入redis 一天
            stringRedisTemplate.opsForValue().set(key, jsonStr, 1, TimeUnit.DAYS);
        }


        if (ObjectUtil.isEmpty(carriage)) {
            throw new SLException(CarriageExceptionEnum.NOT_FOUND);
        }

        // 2.计算计费重量
        //计算重量，最小重量为1kg
        double computeWeight = this.getComputeWeight(waybillDTO, carriage);

        // 3.运费的计算
        // 首重价格
        double firstWeightPrice = carriage.getFirstWeight();
        // 续重价格
        double continuousWeightPrice = carriage.getContinuousWeight();
        // 计算运费
        double expense = firstWeightPrice + ((computeWeight - 1) * continuousWeightPrice);

        // 运费保留一个小数
        expense = NumberUtil.round(expense, 1).doubleValue();

        CarriageDTO dto = CarriageUtils.toDTO(carriage);
        dto.setExpense(expense);
        dto.setComputeWeight(computeWeight);

        return dto;
    }

    /**
     * 根据责任链模式查询运费模板
     *
     * @param waybillDTO
     * @return
     */
    private CarriageEntity findCarriageWithChain(WaybillDTO waybillDTO) {
        return carriageChainHandler.findCarriage(waybillDTO);

    }

    /**
     * 计算计费重量
     *
     * @param waybillDTO
     * @param carriage
     * @return
     */
    private double getComputeWeight(WaybillDTO waybillDTO, CarriageEntity carriage) {

        //计算体积，如果传入体积不需要计算
        Integer volume = waybillDTO.getVolume();
        if (ObjectUtil.isEmpty(volume)) {
            try {
                //长*宽*高计算体积
                volume = waybillDTO.getMeasureLong() * waybillDTO.getMeasureWidth() * waybillDTO.getMeasureHigh();
            } catch (Exception e) {
                //计算出错设置体积为0
                volume = 0;
            }
        }

        // 计算体积重量，体积 / 轻抛系数

        BigDecimal volumeWeight = NumberUtil.div(volume, carriage.getLightThrowingCoefficient(), 1);
        //取大值
        // 向上取整我们前端输入的重量 并且保留一位小数
        // 去最大值  实际重量  体积重量  两个之间获取最大值
        double computeWeight = NumberUtil.max(volumeWeight.doubleValue(), NumberUtil.round(waybillDTO.getWeight(), 1).doubleValue());

        // 下面进行不同重量的货物重量的准确计算
        //计算续重，规则：不满1kg，按1kg计费；10kg以下续重以0.1kg计量保留1位小数；10-100kg续重以0.5kg计量保留1位小数；100kg以上四舍五入取整

        if (computeWeight <= 1) {
            return 1;
        }
        // 原本得到的computeWeight就是四舍五入过后的
        if (computeWeight <= 10) {
            return computeWeight;
        }
        // 举例：
        // 108.4kg按照108kg收费
        // 108.5kg按照109kg收费
        // 108.6kg按照109kg收费
        if (computeWeight >= 100) {
            return NumberUtil.round(computeWeight, 0).doubleValue();
        }

        // 下面就是10 ~ 100kg的计算
        //0.5为一个计算单位，举例：
        // 18.8kg按照19收费，
        // 18.4kg按照18.5kg收费
        // 18.1kg按照18.5kg收费
        // 18.6kg按照19收费
        // 用代码实现

        // 进行向下取整
        int integer = NumberUtil.round(computeWeight, 0, RoundingMode.DOWN).intValue();

        if (NumberUtil.sub(computeWeight, integer) == 0) {
            // 这就是这个数是整数
            return integer;
        }

        // 让computeWeight-integer 看结果是不是大于0.5
        if (NumberUtil.sub(computeWeight, integer) <= 0.5) {
            // 如果大于0.5就是加上0.5
            return NumberUtil.add(integer, 0.5);
        }

        // 下面的情况就是大于0.5  直接让integer+1
        return NumberUtil.add(integer, 1);

    }

    /**
     * 根据前端传递过来的城市id查询运费模板
     *
     * @param waybillDTO
     * @return
     */
    private CarriageEntity findCarriage(WaybillDTO waybillDTO) {
        Long senderCityId = waybillDTO.getSenderCityId();
        Long receiverCityId = waybillDTO.getReceiverCityId();
        if (ObjectUtil.equal(senderCityId, receiverCityId)) {
            // 同城寄

            // 则是同城寄
            CarriageEntity carriage = this.findByTemplateType(CarriageConstant.SAME_CITY);

            if (ObjectUtil.isNotNull(carriage)) {
                return carriage;
            }
        }

        // 远程调用feign 根据城市id查询省的id
        Long receiverProvinceId = this.areaFeign.get(waybillDTO.getReceiverCityId()).getParentId();
        Long senderProvinceId = this.areaFeign.get(waybillDTO.getSenderCityId()).getParentId();
        if (ObjectUtil.equal(receiverProvinceId, senderProvinceId)) {
            // 省内寄
            CarriageEntity carriage = this.findByTemplateType(CarriageConstant.SAME_PROVINCE);
            if (ObjectUtil.isNotNull(carriage)) {
                return carriage;
            }
        }

        // 看是不是经济区互寄
        //经济区互寄
        CarriageEntity carriage = this.findEconomicCarriage(receiverProvinceId, senderProvinceId);

        // 如果不是空就是经济区互寄
        if (ObjectUtil.isNotNull(carriage)) {
            return carriage;
        }

        // 否则就是跨省
        //跨省
        carriage = this.findByTemplateType(CarriageConstant.TRANS_PROVINCE);
        if (ObjectUtil.isNotEmpty(carriage)) {
            return carriage;
        }

        // 如果还是没有就直接抛出异常
        throw new SLException(CarriageExceptionEnum.NOT_FOUND);

    }

    /**
     * 看是不是经济区互寄
     *
     * @param receiverProvinceId
     * @param senderProvinceId
     * @return
     */
    private CarriageEntity findEconomicCarriage(Long receiverProvinceId, Long senderProvinceId) {

        // 获取所有的经济区互寄模板 也就是枚举  我们就可以知道哪一个type对应的省份
        //获取经济区城市配置枚举
        LinkedHashMap<String, EconomicRegionEnum> EconomicRegionMap = EnumUtil.getEnumMap(EconomicRegionEnum.class);

        EconomicRegionEnum economicRegionEnum = null;
        for (EconomicRegionEnum regionEnum : EconomicRegionMap.values()) {
            //该经济区是否全部包含收发件省id
            boolean result = ArrayUtil.containsAll(regionEnum.getValue(), receiverProvinceId, senderProvinceId);
            if (result) {
                // 如果是就是经济区互寄 也就找到了这个经济区
                economicRegionEnum = regionEnum;
                break;
            }
        }
        if (null == economicRegionEnum) {
            //没有找到对应的经济区
            return null;
        }

        // 经济区互寄
        //根据类型编码查询
        LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.lambdaQuery(CarriageEntity.class).eq(CarriageEntity::getTemplateType, CarriageConstant.ECONOMIC_ZONE).eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST)
                // 为什么使用like 引入我们限制了唯一性 而且这个字段在数据也是string类型
                // 又是唯一性，因此可以使用like来确保唯一性
                .like(CarriageEntity::getAssociatedCity, economicRegionEnum.getCode());
        return super.getOne(queryWrapper);

    }


    /**
     * 根据模板类型查询模板，经济区互寄不通过该方法查询模板
     *
     * @param templateType 模板类型：1-同城寄，2-省内寄，4-跨省
     * @return 运费模板
     */
    @Override
    public CarriageEntity findByTemplateType(Integer templateType) {
        LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers
                // 使用工具类创建的
                .<CarriageEntity>lambdaQuery()
                // 根据模板类型查询模板
                .eq(CarriageEntity::getTemplateType, templateType)
                // 这个是普快 目前是固定的
                .eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST);
        return this.getOne(queryWrapper);
    }
}
